#ifndef TINY_VECTOR_HH
#define TINY_VECTOR_HH

#include <initializer_list>

//////////////////////////////////
//	constructors
//////////////////////////////////

template <typename T, unsigned int N>
tiny_vector<T, N>::tiny_vector( const tiny_vector<T, N>& vec )
{
	using array_binary_ops::equal;
	array_binary_apply<equal>::go<true>( begin(), end(), vec.begin() );
}

template <typename T, unsigned int N>
tiny_vector<T, N>::tiny_vector( tiny_vector<T, N>&& vec )
{
	//! [NOTE] this is not really moving elements
	using array_binary_ops::equal;
	array_binary_apply<equal>::go<true>( begin(), end(), vec.begin() );
}

template <typename T, unsigned int N>
tiny_vector<T, N>::tiny_vector( std::initializer_list<T> list )
{
	ASSERT( list.size() <= num_elements, "initialing too many elements" );
	auto iter = begin();
	for( auto v : list )
		*iter++ = v;
}

template <typename T, unsigned int N>
tiny_vector<T, N>::tiny_vector( const T init_num )
{
	for( auto& v : (*this) ) 
		v = init_num;
}

template <typename T, unsigned int N>
tiny_vector<T, N>::tiny_vector( const T init_arr[] )
{
	using array_binary_ops::equal;	
	array_binary_apply<equal>::go<true>( begin(), end(), init_arr );
}

template <typename T, unsigned int N>
template <typename T2>
tiny_vector<T, N>::tiny_vector( const tiny_vector<T2, N>& vec )
{
	auto iter = vec.begin();
	for( auto& v : (*this) ) 
		v = static_cast<T>(*iter++);
}

//////////////////////////////////
//	subscripting
//////////////////////////////////


template <typename T, unsigned int N>
typename tiny_vector<T, N>::num_type& tiny_vector<T, N>::operator[] ( unsigned int i )
{
	ASSERT( range_check(i), "out of range" );
	return data[i];
}

template <typename T, unsigned int N>
const typename tiny_vector<T, N>::num_type& tiny_vector<T, N>::operator[] ( unsigned int i ) const
{
	ASSERT( range_check(i), "out of range" );
	return data[i];
}

template <typename T, unsigned int N>
typename tiny_vector<T, N>::num_type& tiny_vector<T, N>::operator() ( unsigned int i )
{
	ASSERT( range_check(i), "out of range" );
	return data[i];
}

template <typename T, unsigned int N>
const typename tiny_vector<T, N>::num_type& tiny_vector<T, N>::operator() ( unsigned int i ) const
{
	ASSERT( range_check(i), "out of range" );
	return data[i];
}

//////////////////////////////////////////////////////
//	operator overloading -- binary assign
//	-- On const value
//////////////////////////////////////////////////////

#define DECLARE_TINY_VEC_BIN_OPS_ON_CONST_VAL( name, op )		\
template <typename T, unsigned int N >					\
tiny_vector<T, N>& tiny_vector<T, N>::operator op( const T const_val )	\
{									\
	using array_binary_ops::name;					\
	iterator_on_one_element<T> iter_wrap( const_val );		\
	array_binary_apply<name>::go( begin(), end(), iter_wrap );	\
	return *this;							\
}

DECLARE_TINY_VEC_BIN_OPS_ON_CONST_VAL( equal, = );
DECLARE_TINY_VEC_BIN_OPS_ON_CONST_VAL( plus_equal, += );
DECLARE_TINY_VEC_BIN_OPS_ON_CONST_VAL( minus_equal, -= );
DECLARE_TINY_VEC_BIN_OPS_ON_CONST_VAL( multiply_equal, *= );
DECLARE_TINY_VEC_BIN_OPS_ON_CONST_VAL( divide_equal, /= );
DECLARE_TINY_VEC_BIN_OPS_ON_CONST_VAL( mod_equal, %= );
DECLARE_TINY_VEC_BIN_OPS_ON_CONST_VAL( xor_equal, ^= );
DECLARE_TINY_VEC_BIN_OPS_ON_CONST_VAL( bitand_equal, &= );
DECLARE_TINY_VEC_BIN_OPS_ON_CONST_VAL( bitor_equal, |= );

template <typename T, unsigned int N >
tiny_vector<T, N>& tiny_vector<T, N>::operator>>=( const int shift )
{				
	using array_binary_ops::right_shift_equal;
	iterator_on_one_element<int> iter_wrap( shift );		
	array_binary_apply<right_shift_equal>::go( begin(), end(), iter_wrap );		
	return *this;
}

template <typename T, unsigned int N >
tiny_vector<T, N>& tiny_vector<T, N>::operator<<=( const int shift )
{				
	using array_binary_ops::left_shift_equal;
	iterator_on_one_element<int> iter_wrap( shift );		
	array_binary_apply<left_shift_equal>::go( begin(), end(), iter_wrap );		
	return *this;
}

//////////////////////////////////////////////////////
//	operator overloading -- binary assign
//	-- on a tiny_vec
//////////////////////////////////////////////////////

template <typename T, unsigned int N >
template <typename T2>
tiny_vector<T, N>& tiny_vector<T, N>::operator=( const tiny_vector<T2, N>& vec )
{
	using array_binary_ops::equal;
	array_binary_apply<equal>::go<true>( begin(), end(), vec.begin() );
	return *this;
}

#define DECLARE_TINY_VEC_BIN_OPS_ON_TINY_VEC( name, op )		\
template <typename T, unsigned int N >					\
template <typename T2>							\
tiny_vector<T, N>& tiny_vector<T, N>::operator op( const tiny_vector<T2, N>& vec ) \
{									\
	using array_binary_ops::name;					\
	array_binary_apply<name>::go( begin(), end(), vec.begin() );	\
	return *this;							\
}

DECLARE_TINY_VEC_BIN_OPS_ON_TINY_VEC( plus_equal, += );
DECLARE_TINY_VEC_BIN_OPS_ON_TINY_VEC( minus_equal, -= );
DECLARE_TINY_VEC_BIN_OPS_ON_TINY_VEC( multiply_equal, *= );
DECLARE_TINY_VEC_BIN_OPS_ON_TINY_VEC( divide_equal, /= );
DECLARE_TINY_VEC_BIN_OPS_ON_TINY_VEC( mod_equal, %= );
DECLARE_TINY_VEC_BIN_OPS_ON_TINY_VEC( xor_equal, ^= );
DECLARE_TINY_VEC_BIN_OPS_ON_TINY_VEC( bitand_equal, &= );
DECLARE_TINY_VEC_BIN_OPS_ON_TINY_VEC( bitor_equal, |= );
DECLARE_TINY_VEC_BIN_OPS_ON_TINY_VEC( right_shift_equal, >>= );
DECLARE_TINY_VEC_BIN_OPS_ON_TINY_VEC( left_shift_equal, <<= );
	
//////////////////////////////////////////////////////
//	operator overloading -- binary logical
//////////////////////////////////////////////////////

#define DECLARE_TINY_VEC_BIN_LOGIC_OPS_ON_TINY_VEC( name, op )		\
template <typename T, unsigned int N >					\
bool tiny_vector<T, N>::operator op( const tiny_vector<T, N>& vec )	\
{									\
	using array_binary_logical_ops::name;				\
	return	array_binary_logical_apply<name>			\
		::go( begin(), end(), vec.begin() );			\
}

DECLARE_TINY_VEC_BIN_LOGIC_OPS_ON_TINY_VEC( equal, == );
DECLARE_TINY_VEC_BIN_LOGIC_OPS_ON_TINY_VEC( not_equal, != );
/*
DECLARE_TINY_VEC_BIN_LOGIC_OPS_ON_TINY_VEC( greater, > );
DECLARE_TINY_VEC_BIN_LOGIC_OPS_ON_TINY_VEC( less, < );
DECLARE_TINY_VEC_BIN_LOGIC_OPS_ON_TINY_VEC( greater_equal, >= );
DECLARE_TINY_VEC_BIN_LOGIC_OPS_ON_TINY_VEC( less_equal, <= );
*/

//////////////////////////////////
//	IO functions
//////////////////////////////////

#include <iostream>
#include <iomanip>

template <typename T, unsigned int N>
std::ostream& operator<<( std::ostream& out, const tiny_vector<T, N>& vec )
{
	out << "[";
	for( auto v : vec )
		out << setw(10) << v;
	out << setw(10) << "]";
	return out;
}

#endif //!< TINY_VECTOR_HH